package com.wujunshen.tdd;

import com.wujunshen.tdd.common.FlagType;
import com.wujunshen.tdd.flags.BooleanFlag;
import com.wujunshen.tdd.flags.Flag;
import com.wujunshen.tdd.flags.IntegerArrayFlag;
import com.wujunshen.tdd.flags.IntegerFlag;
import com.wujunshen.tdd.flags.StringArrayFlag;
import com.wujunshen.tdd.flags.StringFlag;
import com.wujunshen.tdd.model.Parameter;

import java.util.Arrays;
import java.util.HashSet;
import java.util.Iterator;
import java.util.Set;

import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.text.CharSequenceUtil;
import cn.hutool.core.util.ArrayUtil;
import lombok.extern.slf4j.Slf4j;

import static com.wujunshen.tdd.common.Constants.MINUS_REGEX;
import static com.wujunshen.tdd.common.FlagType.BOOLEAN;
import static com.wujunshen.tdd.common.FlagType.INTEGER;
import static com.wujunshen.tdd.common.FlagType.INTEGER_ARRAY;
import static com.wujunshen.tdd.common.FlagType.STRING;
import static com.wujunshen.tdd.common.FlagType.STRING_ARRAY;

/** @author wujunshen */
@Slf4j
public class Args {
  private final Set<String> flagSet = new HashSet<>();
  private final Flag<Boolean> booleanFlag = new BooleanFlag();
  private final Flag<Integer> integerFlag = new IntegerFlag();
  private final Flag<Integer[]> integerArrayFlag = new IntegerArrayFlag();
  private final Flag<String> stringFlag = new StringFlag();
  private final Flag<String[]> stringArrayFlag = new StringArrayFlag();

  public static void main(String[] args) {
    Args test = new Args();
    test.setParameter("b", BOOLEAN);
    test.setParameter("i", INTEGER);
    test.setParameter("s", STRING);
    test.setParameter("o", STRING);
    test.setParameter("I", INTEGER_ARRAY);
    test.setParameter("S", STRING_ARRAY);

    test.parse(new String[] {"-i", "92", "-s", "a", "-b", "-I", "8089,8088,8087", "-S", "a,b"});
    log.info(
        "boolean value:{},integer value:{},integer array value:{},string value:{},string array value:{}\n",
        test.getBoolean("b"),
        test.getInteger("i"),
        Arrays.toString(test.getIntegerArray("I")),
        test.getString("s"),
        Arrays.toString(test.getStringArray("S")));

    test.parse(new String[] {"-i", "92", "-s", "a", "-I", "8089,8088,8087", "-S", "a,b"});
    log.info(
        "boolean value:{},integer value:{},integer array value:{},string value:{},string array value:{}\n",
        test.getBoolean("b"),
        test.getInteger("i"),
        Arrays.toString(test.getIntegerArray("I")),
        test.getString("s"),
        Arrays.toString(test.getStringArray("S")));

    test.parse(new String[] {"-i", "92", "-s", "a", "-I", "8089,8088,8087"});
    log.info(
        "boolean value:{},integer value:{},integer array value:{},string value:{},string array value:{}\n",
        test.getBoolean("b"),
        test.getInteger("i"),
        Arrays.toString(test.getIntegerArray("I")),
        test.getString("s"),
        Arrays.toString(test.getStringArray("S")));

    test.parse(new String[] {"-s", "/logs/a.log", "-o", "/json/a.json"});
    log.info(
        "boolean value:{},string value:{},string value:{}\n",
        test.getBoolean("b"),
        test.getString("s"),
        test.getString("o"));
  }

  public void setParameter(String flag, FlagType type) {
    initParameters(flag, type);

    flagSet.add(flag);
  }

  public void parse(String[] args) {
    if (ArrayUtil.isEmpty(args) || CharSequenceUtil.isBlank(args[0])) {
      updateValue(args);
      return;
    }

    updateValue(args);

    Iterator<String> argIter = CollUtil.toList(args).iterator();

    String arg;
    String flag;
    while (argIter.hasNext()) {
      arg = argIter.next().trim();

      existedMinus(arg);

      flag = arg.substring(1);

      checkFlags(flag);

      setFlags(flag, argIter);
    }
  }

  public boolean getBoolean(String flag) {
    return booleanFlag.getValue(flag);
  }

  public int getInteger(String flag) {
    return integerFlag.getValue(flag);
  }

  public Integer[] getIntegerArray(String flag) {
    return integerArrayFlag.getValue(flag);
  }

  public String getString(String flag) {
    return stringFlag.getValue(flag);
  }

  public String[] getStringArray(String flag) {
    return stringArrayFlag.getValue(flag);
  }

  private void initParameters(String flag, FlagType type) {
    booleanFlag.initParameters(flag, type);
    integerFlag.initParameters(flag, type);
    integerArrayFlag.initParameters(flag, type);
    stringFlag.initParameters(flag, type);
    stringArrayFlag.initParameters(flag, type);
  }

  private void updateValue(String[] args) {
    for (Parameter<Boolean> parameter : BooleanFlag.BOOLEAN_PARAMETERS) {
      if (!ArrayUtil.contains(args, parameter.getFlag())) {
        parameter.setValue(false);
      } else {
        parameter.setValue(true);
      }
    }
  }

  private void existedMinus(String arg) {
    if (!arg.matches(MINUS_REGEX)) {
      throw new IllegalArgumentException("arg without minus: -");
    }
  }

  private void checkFlags(String flag) {
    if (!CollUtil.contains(flagSet, flag)) {
      throw new IllegalArgumentException("setting undefined flag: " + flag);
    }
  }

  private void setFlags(String flag, Iterator<String> argIter) {
    booleanFlag.setValue(flag, argIter);
    integerFlag.setValue(flag, argIter);
    stringFlag.setValue(flag, argIter);
    integerArrayFlag.setValue(flag, argIter);
    stringArrayFlag.setValue(flag, argIter);
  }
}
